﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Gemli.Collections;
using System.Reflection;

namespace Gemli.Data
{
    /// <summary>
    /// Wraps a CLR object to make it a database-mapped
    /// <see cref="DataModel"/>.
    /// </summary>
    /// <typeparam name="TEntity">
    /// Any business object with public properties or fields 
    /// that this object is wrapping as a <see cref="DataModel"/>.
    /// </typeparam>
    [Serializable]
    public partial class DataModel<TEntity> : DataModel
    {
        /// <summary>
        /// Constructs a <see cref="DataModel{TEntity}"/> wrapper object,
        /// initializing it with the specified 
        /// <typeparamref name="TEntity">type</typeparamref>.
        /// </summary>
        public DataModel() : base(typeof(TEntity), Activator.CreateInstance(typeof(TEntity)))
        {
        }
        /// <summary>
        /// Creates a <see cref="DataModel"/> wrapper for 
        /// the specified <paramref name="modelInstance"/>.
        /// </summary>
        /// <param name="modelInstance"></param>
        public DataModel(TEntity modelInstance)
            : base(typeof(TEntity), modelInstance)
        {
        }

        /// <summary>
        /// Exposes inner data dictionary to public scope.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public new object this[string key]
        {
            get { return base[key]; }
            set { base[key] = value;}
        }

        /// <summary>
        /// Gets the unwrapped CLR object that this <see cref="DataModel"/> wrapper
        /// represents to the database.
        /// </summary>
        public new TEntity Entity
        {
            get { return (TEntity)base.Entity; }
            set { base.Entity = value;}
        }

        private readonly Type Type = typeof(TEntity);

        private new CaseInsensitiveDictionary<object> InnerData
        {
            get { return base.InnerData; }
        }

        /// <summary>
        /// Prepares the 
        /// data to propagate to or from the <see cref="Entity"/> before 
        /// the Entity is retrieved or before data changes are made and/or saved.
        /// </summary>
        /// <param name="syncTo"></param>
        public override void SynchronizeFields(SyncTo syncTo)
        {
            if (this.Type.IsDataModel() &&
                Type == GetUnwrappedType(Type)) return;
            switch (syncTo)
            {
                case SyncTo.FieldMappedData:
                    foreach (var field_kvp in this.EntityMappings.FieldMappings)
                    {
                        var field = field_kvp.Value;

                        // todo: optimize with cached Action<T>
                        // see: http://msmvps.com/blogs/jon_skeet/archive/2008/08/09/making-reflection-fly-and-exploring-delegates.aspx

                        if (field.TargetMember is FieldInfo)
                        {
                            var val = ((FieldInfo) field.TargetMember).GetValue(this.Entity);
                            var dval = this[field.TargetMember.Name];
                            if (((dval == null) != (val == null)) ||
                                (dval != null && !dval.Equals(val)))
                            {
                                this[field.TargetMember.Name] = val;
                            }
                        }
                        else if (field.TargetMember is PropertyInfo)
                        {
                            var val = ((PropertyInfo)field.TargetMember).GetValue(this.Entity, new object[] { });
                            var dval = this[field.TargetMember.Name];
                            if (((dval == null) != (val == null)) ||
                                (dval != null && !dval.Equals(val)))
                            {
                                this[field.TargetMember.Name] = val;
                            }
                        }
                    }
                    break;
                case SyncTo.ClrMembers:
                    foreach (var field_kvp in this.EntityMappings.FieldMappings)
                    {
                        var field = field_kvp.Value;

                        // todo: optimize with cached Action<T>
                        // see: http://msmvps.com/blogs/jon_skeet/archive/2008/08/09/making-reflection-fly-and-exploring-delegates.aspx

                        if (field.TargetMember is FieldInfo)
                        {
                            var clrval = ((FieldInfo)field.TargetMember).GetValue(this.Entity);
                            var dicval = this[field.TargetMember.Name];
                            if (((clrval == null) != (dicval == null)) ||
                                (dicval != null && !dicval.Equals(clrval)))
                            {
                                ((FieldInfo) field.TargetMember).SetValue(
                                    this.Entity, this[field.TargetMember.Name]);
                            }
                        }
                        else if (field.TargetMember is PropertyInfo)
                        {
                            var propval = ((PropertyInfo)field.TargetMember).GetValue(this.Entity, new object[] { });
                            var dicval = this[field.TargetMember.Name];
                            if (((dicval == null) != (propval == null)) ||
                                (dicval != null && !dicval.Equals(propval)))
                            {
                                ((PropertyInfo) field.TargetMember).SetValue(
                                    Entity, dicval, new object[] {});
                            }
                        }
                    }
                    break;
            }
        }

        /// <summary>
        /// Creates and returns a new <see cref="DataModelQuery&lt;T&gt;"/> object
        /// that is typed for this wrapper.
        /// </summary>
        /// <returns></returns>
        public static new DataModelQuery<DataModel<TEntity>> NewQuery()
        {
            return new DataModelQuery<DataModel<TEntity>>();
        }

    }
}
